package classifier;

import java.util.Random;
import java.util.Vector;

import data.Data;

public class KMeans<G extends Data<G>> 
{

	public KMeans(Vector<G> data, int k)
	{
		x = new Vector<Pair<G,Integer>> ();
		for(G g : data)
		{
			x.add(new Pair<G,Integer>(g,0)); 
		}

		K = k;
		rand = new Random();
	}
	
	public Vector<Pair<G,Integer>> getClustering()
	{
		return x;
	}
		
	public void run() {
		
			//System.out.println("iteration: " + i);
			minimizeDist();
			estimateMeans();
		
	}
	
	public void run(int iterations)
	{
		initialize();
		for(int i=0; i< iterations; i++)
		{
			//System.out.println("iteration: " + i);
			minimizeDist();
			estimateMeans();
		}

                /*
		for(int i=0; i<K; i++)
		{
			System.out.println(means.get(i).toString());
		}
                */
                
	}
	
	public Vector<G> getMeans()
	{
		return means;
	}
	
	/**
	 * Initialize the seeds for the means.
	 */
	public void initialize()
	{
		means = new Vector<G>();
		Vector<Integer> usedIndexes = new Vector<Integer>();

		for(int i=0; i< K; i++)
		{
			int idx;
			do {
				idx = rand.nextInt(x.size()-1);
			} while (usedIndexes.contains(idx)); //we don't want two seeds to be identical

			usedIndexes.add(idx);
			means.add(x.get(idx).getFirst());
		}
	}
	
	private void minimizeDist()
	{
		for(Pair<G,Integer> d : x)
		{
			double min = Double.MAX_VALUE;
			int argmin = Integer.MAX_VALUE;
			for(int j=0; j< K; j++)
			{
				double sim = d.getFirst().getDistance(means.get(j));
				if(sim < min)
				{
					min = sim;
					argmin = j;
				}
			}
			d.setSecond(argmin);
		}
	}
	
	private void estimateMeans()
	{
		means = new Vector<G>();
		Vector<Integer> meansCount = new Vector<Integer>();
		for(int i=0; i<K; i++)
		{
			means.add(x.get(0).getFirst().getZero()); //David: you just want to create a new mean for every cluster, right?
			meansCount.add(0);
		}
		// Sum all data points that belong to the same class.
		for(Pair<G,Integer> d : x)
		{
			means.set(d.getSecond(), means.get(d.getSecond()).add(d.getFirst()));
			meansCount.set(d.getSecond(), meansCount.get(d.getSecond())+ 1);
		}

		// Scale means
		for(int i=0; i<K; i++)
		{
			means.set(i, means.get(i).scale( 1.0/meansCount.get(i) )); //David: since scale is an identity function, that's fine, since the means are already normalized
		}
		
	}
	
	private Vector<Pair<G,Integer>> x;
	private Vector<G> means;
	private int K;
	private Random rand;
	
}
